/**
 * @class 深空天体实体（精确参数）= 河内双星+河内星云+河内星团+河外本群星系+河外本超团星系
 * @author DuskyHuang 圣子
*/

import { _decorator, Component, Node, Vec3, MeshRenderer, Camera, Material } from 'cc';
import { core, Notifications, utils } from 'cfw';
import { SEC_PER_MIN, SEC_PER_HOUR, DEG_PER_HOUR, NEAREST_STAR, DeepSkyObjectType, DeepNameType, SceneGraph, Res, LOCAL_TO_SOLAR, VIRGO_TO_SOLAR } from 'const';
import { polar2cartesian, equator2ecliptic, pyramidCulling, appearanceToRadiusFov, attenuate } from 'algorithms';
import { CosmosProxy, deepGalaxy, ui, UIProxy } from 'proxies';
import { setLabelStyle } from 'helpers';
import { CelestialLabel } from '../ui/items/celestial-label';
import { CelestialNameType } from '../ui/label-i18n-adv';
import { Task, TaskNames } from 'notifications';
const { ccclass, property } = _decorator;

@ccclass('cocosmos.DeepSkyFixed')
export class DeepSkyFixed extends Component implements ICulling {

    @property(Material) public messierInners: Material = null;
    @property(Material) public messierGalaxies: Material = null;
    public get routing(): boolean { return false; } // TODO

    public get fov(): num.deg { return this._fov; }
    public get config(): deepSkyConfig { return this._config; }
    public set viewer(target: Node) { this._viewer = target; }
    public get milky(): boolean { return this._milky; }
    public get clustered(): boolean { return this._clustered; }
    public get nebular(): boolean { return this._nebular; }
    public get local(): boolean { return this._local; }
    public get extragalactic(): boolean { return this._extragalactic; }
    public get label(): CelestialLabel { return this._label; }
    public set label(arg: CelestialLabel) { this._label = arg; }
    public get culling(): boolean { return this._culling; }

    public loadConfig(key: string, config: deepSkyConfig): true | void {
        this._config = config;
        const { ra_hms, dec_hms, dist, dso } = this._config;
        const [H, M, S] = ra_hms.split(':');
        this._config.RA = ((+H) + (+M) / SEC_PER_MIN + (+S) / SEC_PER_HOUR) * DEG_PER_HOUR;
        const [h, m, s] = dec_hms.split(':');
        this._config.Dec = ((+h) + (+m) / SEC_PER_MIN + (+s) / SEC_PER_HOUR);
        this.node.name = key;
        this._milky = dist > NEAR_STAR_KLY && dist < MILKY_KLY;
        this._clustered = dso < EmissiveNebula;
        this._nebular = !this.clustered && dso < EllipticalGalaxy;
        this._extragalactic = !this.clustered && !this.nebular;
        this._local = this._extragalactic && dist < LOCAL_KLY;
        return this._initAccurate();
    }

    protected onLoad(): void {
        this._mesh = this.getComponent(MeshRenderer);
        core.on(Notifications.Time.TICK, this.onTick, this);
    }

    public static LoadTexTaskLaunched: boolean = false;

    protected onDestroy(): void {
        this.viewer = null;
        core.unload(this);
    }

    protected onTick(dt?: time.sec, tick?: num.int): void {
        this._syncActivating();
        this._syncInstancing();
        this._syncNameLabel();
    }

    private _syncActivating(): void {
        this._culling = false;
        if (!this.node.parent || !this.node.parent.activeInHierarchy) return;
        const { showNebulas, showClusters, showGalaxies } = deepGalaxy;
        const activated = (Vec3.squaredDistance(this.node.worldPosition, Vec3.ZERO) > WPS_VLV_SQR)
            && ((this._nebular && showNebulas) || (this._clustered && showClusters) || (this._extragalactic && showGalaxies));
        if (activated) {
            this._culling = pyramidCulling(this._viewer.getComponent(Camera), this.node, FOV_CULLING_VALVE);
            this.node.active = !this._culling;
        } else this.node.active = false;
    }

    private _syncInstancing(): void {
        this.node.activeInHierarchy && this._mesh.setInstancedAttribute('instanced_uv', this._uvArr);
    }

    private _syncNameLabel(): void {
        const style = this._clustered ? 'cluster' : this._nebular ? 'nebula' : 'galaxy';
        const bingo = style === 'galaxy' ? ui.showGalaxyNames : ui.showNebularNames;
        const show = this.node.activeInHierarchy && bingo;
        if (!show && this._label) {
            this._label.free();
            this._label = null;
        } else if (show) {
            if (!this._label) {
                const label = CelestialLabel.claim(style, this.node.name);
                label.camera = utils.findCamera(COSMOS, DEEP);
                label.target = this.node;
                let north = this.node.worldPosition.y > 0;
                if (style === 'galaxy') {
                    const { dso, cent } = this._config
                    const sub = (dso == DwarfGalaxy) ? (!!cent ? 'companion' : 'dwarf') : 'center';
                    if (sub != 'companion') north = this.node.position.y > (this.local ? LOCAL_TO_SOLAR.y : VIRGO_TO_SOLAR.y);
                    this._label = setLabelStyle(label, style, sub, north);
                } else {
                    this._label = setLabelStyle(label, style, north);
                }
                const { bias, NGC, IC, nick } = this._config;
                this._label.setMap({
                    [DeepNameType.NAMED]: { en: bias, cn: nick },
                    [DeepNameType.MESSIER]: this.node.name,
                    [DeepNameType.NGC_IC]: NGC ? ('NGC-' + NGC) : IC ? ('IC-' + IC) : '',
                }, CelestialNameType.Deep);
            }
            this._attenuateLabel();
        }
    }

    private _attenuateLabel(): void {
        if (!this._label) return;
        const lenSqr = Vec3.squaredDistance(this.node.worldPosition, Vec3.ZERO);
        this._label.alpha = attenuate(lenSqr, NameLabelAtt, true);
    }

    private _initAccurate(): true | void {
        const { RA, Dec, dist, diam, appD, tex } = this._config;
        if (isNaN(RA)) return console.warn(`[${this.node.name}]:ra`, RA);
        if (isNaN(Dec)) return console.warn(`[${this.node.name}]:dec`, Dec);
        if (isNaN(dist)) return console.warn(`[${this.node.name}]:dist`, dist);
        polar2cartesian(RA, Dec, dist, v3a);
        if (isNaN(v3a.x) || isNaN(v3a.y) || isNaN(v3a.z)) return console.warn(`[${this.node.name}]:pos`, v3a);
        equator2ecliptic(v3a, v3a);
        const pow10 = (!this.extragalactic ? RAT_MILKY : this.local ? RAT_LOCAL : RAT_VIRGO);
        v3a.multiplyScalar(Math.pow(10, pow10 + POW_KILO));
        const [radius, fov] = appearanceToRadiusFov(dist, diam, appD, pow10);
        const magnify = this.extragalactic ? (this.local ? 1.3 : 2.5) : 1;
        v3b.set(radius, radius, radius).multiplyScalar(magnify);
        this._fov = +(fov).toFixed(1);
        this.node.setPosition(v3a);
        this.node.setScale(v3b);
        this.getComponent(MeshRenderer).setSharedMaterial(this._extragalactic ? this.messierGalaxies : this.messierInners, 0);
        const [idx, span] = tex ?? (this._fov > 10 ? MESSIER_DEFAULT_BIG : MESSIER_DEFAULT);
        const xy = span * CELL;
        const z = (idx % MESSIER_CELL_NUM) * CELL;
        const w = Math.floor(idx / MESSIER_CELL_NUM) * CELL;
        this._uvArr = [xy, xy, z, w];
        return true;
    }

    private _label: CelestialLabel = null;
    private _viewer: Node;
    private _config: deepSkyConfig;
    private _fov: num.deg;
    private _mesh: MeshRenderer;
    private _culling: boolean;
    private _milky: boolean = false;
    private _local: boolean = false;
    private _clustered: boolean = false;
    private _nebular: boolean = false;
    private _extragalactic: boolean = false;
    private _uvArr = [1, 1, 0, 0];
    private _nstr = '';
}

const { COSMOS, Cameras: { DEEP } } = SceneGraph;
const NEAR_STAR_KLY = NEAREST_STAR / 1000;
const MILKY_KLY = 100;
const LOCAL_KLY = 10000;
const POW_KILO = 3;
const { MilkyWay, LocalGG, VirgoSC } = CosmosProxy.Scopes;
const RAT_MILKY = -MilkyWay[2];
const RAT_LOCAL = -LocalGG[2];
const RAT_VIRGO = -VirgoSC[2];
const { EmissiveNebula, EllipticalGalaxy, DwarfGalaxy } = DeepSkyObjectType;
// const { MESSIER, NGC_IC, CHINESE, WEST_BIAS } = DeepNameType;
const { NameLabelAtt, WPS_CULLING_VALVE: WPS_NEAREST_VALVE, FOV_CULLING_VALVE } = UIProxy;
const WPS_VLV_SQR = WPS_NEAREST_VALVE * WPS_NEAREST_VALVE;
const { MESSIER_CELL_NUM, MESSIER_DEFAULT, MESSIER_DEFAULT_BIG } = Res.Sheets;
const v3a = new Vec3;
const v3b = new Vec3;
const CELL = 255 / MESSIER_CELL_NUM;